package ggaviator

import (
	"context"
	"errors"
	"fmt"
	"github.com/nats-io/nats.go"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"strconv"
	"sync"
	"sync/atomic"
	"time"
	"wzgames/internal/codec/sfs"
	"wzgames/internal/constant"
	"wzgames/internal/gdao/aviatordao"
	"wzgames/internal/gloader"
	"wzgames/internal/model/aviatorp"
	"wzgames/internal/model/gamep"
	"wzgames/internal/response"
	"wzgames/internal/services/games"
	"wzgames/pkg/actor/actor"
	"wzgames/pkg/actor/actorservice"
	"wzgames/pkg/common/timingwheel"
	"wzgames/pkg/logger"
	"wzgames/pkg/remote/etcd"
	"wzgames/pkg/utils/dateutil"
)

// 处理contower的播报，状态同步等

type AirCraft struct {
	mu  sync.RWMutex
	pp  *GAviator
	pid *actor.PID
	// 轮次信息和状态
	roundDir string
	stateDir string
	evtch    chan *etcd.KvEvt // etcd数据监听
	stopCh   chan struct{}

	state       AcState // 当前状态
	timeLeft    int64
	cfg         *aviatorp.AviatorConfig
	notifiers   []*Notifier // 播报分组
	notifierIdx int32

	roundInfo  *aviatorp.AviatorRound           // 当前轮次信息
	multiplier *AMultiplier                     // 同步效验播报
	multiChan  chan *aviatorp.CurrentMultiplier // 接收contower播报chan
	currMulti  float64
	stopMulti  chan struct{}
	timerWheel *timingwheel.TimingWheel
	avtDao     aviatordao.AviatorDao

	roundCounts   map[int64]*Counter // 结算用
	keyMultiplier string
	multiSub      *nats.Subscription // 监听赔率播报
	keyBetsRet    string
	betsSub       *nats.Subscription // 监听投注播报
}

func newAirCraft(pp *GAviator) *AirCraft {
	ac := &AirCraft{
		pp:          pp,
		evtch:       make(chan *etcd.KvEvt, 1),
		stopCh:      make(chan struct{}, 1),
		multiChan:   make(chan *aviatorp.CurrentMultiplier, 256),
		stopMulti:   make(chan struct{}, 1),
		state:       AcState{State: constant.UnActive},
		multiplier:  &AMultiplier{},
		timerWheel:  timingwheel.NewTimingWheel(time.Millisecond, 100),
		keyBetsRet:  constant.AvtBetRetNKeySub + "." + pp.InstIdStr(),
		roundCounts: make(map[int64]*Counter),
	}

	name := fmt.Sprintf("aircraft-%s", strconv.FormatInt(ac.pp.GameInstId(), 10))
	props := actor.PropsFromProducer(func() actor.Actor { return ac })
	pid, err := actorservice.ActorSystem().Root.SpawnNamed(props, name)
	if err != nil {
		logger.Error("[Aviator] spawn aviator aircraft actor error", zap.Int64("InstId", ac.pp.GameInstId()), zap.Error(err))
		return nil
	}
	ac.pid = pid
	ac.multiplier.Init(ac)
	ac.avtDao = aviatordao.AviatorDao{Driver: gloader.CommMgr().GetDBProxy(constant.MysqlOfAviator).Ds()}

	return ac
}

func (ac *AirCraft) Pid() *actor.PID {
	return ac.pid
}

func (ac *AirCraft) Init(cfg *aviatorp.AviatorConfig) error {
	ac.cfg = cfg
	// etcd路径
	ac.roundDir = fmt.Sprintf("%s/round-%v", constant.CcAirCraft, ac.pp.GameInstId())
	ac.stateDir = fmt.Sprintf("%s/state", ac.roundDir)
	err := ac.loadRound() // etcd加载当前轮次信息和状态
	if err != nil {
		return err
	}
	ac.multiplier.SetHouseEdge(ac.cfg.HouseEdge)
	go ac.onData() // 监听ETCD状态
	// 初始化播报分组
	for i := int32(0); i < cfg.Notifiers; i++ {
		ac.notifiers = append(ac.notifiers, newNotifier(cfg.NotifyChanSize, ac))
	}
	ac.timerWheel.Start()
	go ac.onMultiplier() // 接收播报通知

	multiSub, err1 := gloader.CommMgr().NatsC().Subscribe(constant.AvtMultiplierNKeySub+"."+ac.pp.InstIdStr(), ac.doMultiplier)
	if err1 != nil {
		logger.Error("[Aviator] start multiplier sub fail on subscribe", zap.String("RoundDir", ac.roundDir), zap.String("SubKey", constant.AvtMultiplierNKeySub), zap.Error(err1))
		return err1
	}
	ac.multiSub = multiSub

	betsSub, err2 := gloader.CommMgr().NatsC().Subscribe(ac.keyBetsRet, ac.doBetsRet)
	if err2 != nil {
		logger.Error("[Aviator] start bets notify sub fail on subscribe", zap.String("RoundDir", ac.roundDir), zap.String("SubKey", ac.keyBetsRet), zap.Error(err2))
		return err2
	}
	ac.betsSub = betsSub
	ac.resetCurrMulti()
	return ac.watchRound()
}

func (ac *AirCraft) Update(cfg *aviatorp.AviatorConfig) {
	ac.mu.Lock()
	defer ac.mu.Unlock()
	if cfg.Notifiers > ac.cfg.Notifiers {
		diff := cfg.Notifiers - ac.cfg.Notifiers
		for i := int32(0); i < diff; i++ {
			ac.notifiers = append(ac.notifiers, newNotifier(cfg.NotifyChanSize, ac))
		}
	}
	ac.cfg = cfg
	ac.multiplier.SetHouseEdge(ac.cfg.HouseEdge)
}

func (ac *AirCraft) resetCurrMulti() {
	ac.currMulti = 1.0
}

func (ac *AirCraft) loadRound() error {
	err := gloader.CClient().GetRawValues(ac.roundDir, func(key string, data []byte) error {
		if key == ac.roundDir {
			return ac.parseRound(data)
		} else if key == ac.stateDir {
			return ac.parseState(data)
		}
		return nil
	})
	if err != nil {
		logger.Error("[Aviator] load aviator round info fail", zap.String("RoundDir", ac.roundDir), zap.Error(err))
		return err
	}
	return nil
}

func (ac *AirCraft) watchRound() error {
	err := gloader.CClient().Watch(context.Background(), ac.roundDir, true, ac.evtch)
	if err != nil {
		logger.Error("[Aviator] watch aircraft round fail", zap.String("RoundDir", ac.roundDir), zap.Error(err))
		return err
	}

	return nil
}

// parseRound 小飞机轮次数据
func (ac *AirCraft) parseRound(data []byte) error {
	if data == nil {
		return errors.New("[Aviator] invalid round data")
	}
	rd := &aviatorp.AviatorRound{}
	err := proto.Unmarshal(data, rd)
	if err != nil {
		logger.Error("[Aviator] aviator parse aircraft round fail on Unmarshal", zap.String("RoundDir", ac.roundDir), zap.Error(err))
		return err
	}
	ac.mu.Lock()
	ac.roundInfo = rd
	_, ok := ac.roundCounts[rd.RoundId]
	if !ok {
		// 启动对应轮次的Counter
		ac.roundCounts[rd.RoundId] = NewCounter(rd.RoundId, ac.pid)
	}
	ac.mu.Unlock()
	return nil
}

// parseState 小飞机状态数据
func (ac *AirCraft) parseState(data []byte) error {
	if data == nil {
		return errors.New("[Aviator] invalid state data")
	}
	nState := &aviatorp.AirCraftState{}
	err := proto.Unmarshal(data, nState)
	if err != nil {
		logger.Error("[Aviator] aviator parse aircraft state fail on Unmarshal", zap.String("RoundDir", ac.roundDir), zap.Error(err))
		return err
	}
	newState := AcState{
		RoundId:       nState.RoundId,
		State:         nState.NewStateId,
		StateStart:    nState.StateStart, // 状态开始时间
		End:           nState.RoundEnd,
		MaxMultiplier: nState.MaxMultiplier, // 小飞机crash值
		TimeLeft:      nState.TimeLeft,
		Dateline:      dateutil.UnixMilli(), // 记录时间
		ExpectTime:    nState.ExpectTime,    // deprecated
	}
	if nState.TimeLeft > 0 {
		ac.timeLeft = nState.TimeLeft
	}
	ac.mu.Lock()
	preState := ac.state.State
	ac.state = newState
	actorservice.Send(ac.pid, newState) // 播报状态
	if preState == constant.BetState && newState.State == constant.FlyState {
		// TODO 飞行状态开始，本游戏节点同步启动播报校验
		ac.resetCurrMulti()
		ac.multiplier.Reset()
		ac.multiplier.Run(nState.RoundId, nState.MaxMultiplier)
	}
	ac.mu.Unlock()
	if newState.State == constant.CrashState {
		// crash后开始本轮次结算
		ac.multiplier.Reset()
		actorservice.Send(ac.pid, RoundCounterSettle{RoundId: newState.RoundId, MaxMultiplier: newState.MaxMultiplier})
	}
	return nil
}

func (ac *AirCraft) RoundInfo() (int64, int32) {
	ac.mu.RLock()
	defer ac.mu.RUnlock()
	return ac.state.RoundId, ac.state.State
}

// CanBet 是否可以下注
func (ac *AirCraft) CanBet() (bool, int64) {
	ac.mu.RLock()
	if ac.roundInfo == nil {
		ac.mu.RUnlock()
		return false, 0
	}
	state := ac.state.State
	roundId := ac.roundInfo.RoundId
	dateline := ac.state.Dateline
	ac.mu.RUnlock()
	t := dateutil.UnixMilli()
	// 投注状态且在规定的时间内
	if state == constant.BetState && t <= dateline+ac.timeLeft*1000 {
		return true, roundId
	}
	return false, 0
}

func (ac *AirCraft) CurrentMultiplier() float64 {
	return ac.currMulti
}

// TimerAfter 定时器
func (ac *AirCraft) TimerAfter(d time.Duration, fn func(interface{}), args interface{}) *timingwheel.Timer {
	return ac.timerWheel.AfterFunc(d, fn, args)
}

// crash 本节点效验crash 状态变更
func (ac *AirCraft) crash(roundId int64, maxMultiplier float64) {
	ac.mu.Lock()
	if ac.state.State == constant.FlyState {
		ac.state.State = constant.CrashState
	}
	ac.mu.Unlock()
	ac.updateAvtNodeRound(roundId, maxMultiplier)
}

// CanCashOut 判断是否可以CashOut操作
func (ac *AirCraft) CanCashOut() (bool, int64, float64, float64) {
	ac.mu.RLock()
	if ac.roundInfo == nil {
		ac.mu.RUnlock()
		return false, 0, 0, 0
	}
	state := ac.state.State
	roundId := ac.roundInfo.RoundId
	currMultiplier := ac.currMulti
	maxMulti := ac.state.MaxMultiplier
	//expectTime := ac.state.ExpectTime
	//stateStart := ac.state.StateStart
	ac.mu.RUnlock()
	//expectTime += stateStart + 50
	//t := dateutil.UnixMilli()
	if state == constant.FlyState { // 飞行状态中可以CashOut
		return true, roundId, currMultiplier, maxMulti
	}
	return false, 0, 0, 0
}

// updateAvtNodeRound 小飞机在该游戏节点上的状态日志
func (ac *AirCraft) updateAvtNodeRound(roundId int64, maxMultiplier float64) {
	ent := &aviatorp.AvtNodeRoundEntity{
		NodeId:   gloader.BootConfig().Node().NodeId,
		InstId:   ac.pp.GameInstId(),
		RoundId:  roundId,
		Status:   constant.CrashState,
		Result:   maxMultiplier,
		Dateline: dateutil.UnixMilli(),
	}
	_ = ac.avtDao.CreateAvtNodeRound(ent)
}

// onData 小飞机ETCD状态数据变更
func (ac *AirCraft) onData() {
	for {
		select {
		case e := <-ac.evtch:
			switch e.Type {
			case etcd.KvEventNew, etcd.KvEventUpdate:
				if e.Key == ac.roundDir {
					_ = ac.parseRound(e.Value)
				} else if e.Key == ac.stateDir {
					_ = ac.parseState(e.Value)
				}
			case etcd.KvEventDelete:

			}
		case <-ac.stopCh:
			return
		}
	}
}

// doMultiplier nats消息处理
func (ac *AirCraft) doMultiplier(msg *nats.Msg) {
	c := response.AcquireAvtMulti()
	err := proto.Unmarshal(msg.Data, c)
	if err != nil {
		logger.Error("[Aviator] do multiplier fail on proto.Unmarshal", zap.String("RoundDir", ac.roundDir), zap.Error(err))
		return
	}
	if ac.state.RoundId != c.RoundId {
		return
	}
	if c.CurrentPayout >= ac.currMulti {
		ac.currMulti = c.CurrentPayout
	}
	ac.multiChan <- c
}

// onMultiplier 播报
func (ac *AirCraft) onMultiplier() {
	for {
		select {
		case m := <-ac.multiChan:
			roundId := m.RoundId
			multi := m.CurrentPayout
			timeElp := m.CurrentGameTime
			maxMulti := m.MaxMultiplier
			crash := m.Crash
			response.ReleaseAvtMulti(m)
			for _, nt := range ac.notifiers {
				nt.NotifyMulti(roundId, multi, maxMulti, timeElp, crash)
			}
		case <-ac.stopMulti:
			return
		}
	}
}

// doBetsRet nats消息处理
func (ac *AirCraft) doBetsRet(msg *nats.Msg) {
	ntf := &gamep.BetsNotify{}
	err := proto.Unmarshal(msg.Data, ntf)
	if err != nil {
		logger.Error("[Aviator] do bets notify fail on proto.Unmarshal", zap.String("RoundDir", ac.roundDir), zap.Error(err))
		return
	}
	actorservice.Send(ac.pid, ntf)
}

func (ac *AirCraft) NextRoundSeedH256() string {
	if ac.roundInfo != nil && ac.roundInfo.Next != nil {
		return ac.roundInfo.Next.GetH256()
	}
	return constant.EmptyStr
}

func (ac *AirCraft) ServerSeed() string {
	if ac.roundInfo != nil {
		return ac.roundInfo.Seed
	}
	return constant.EmptyStr
}

// Receive Actor接收处理消息
func (ac *AirCraft) Receive(ctx actor.Context) {
	switch m := ctx.Message().(type) {
	case AcState: // 播报状态
		ac.broadcastState(m)
	case *gamep.BetsNotify: // 播报投注 CashOut等消息
		ac.broadcastBetsNotify(m)
	case RoundCounterSettle: // 轮次结算
		ac.RoundCounterSettle(m.RoundId, m.MaxMultiplier)
	case RoundCounterRemove:
		ac.removeRoundCounter(m.RoundId)
	}
}

// removeRoundCounter 轮次结算后移除对应的Counter
func (ac *AirCraft) removeRoundCounter(roundId int64) {
	defer logger.Recover()
	ac.mu.RLock()
	ct, ok := ac.roundCounts[roundId]
	ac.mu.RUnlock()
	if ok {
		ct.Clear()
		ac.mu.Lock()
		delete(ac.roundCounts, roundId)
		ac.mu.Unlock()
	}
}

// PlayAction 玩家投注 CashOut 取消等动作日志
func (ac *AirCraft) PlayAction(betId uint64, roundId int64, tp int8) {
	ac.mu.RLock()
	ct, ok := ac.roundCounts[roundId]
	ac.mu.RUnlock()
	if ok {
		ct.Action(betId, tp)
	}
}

// RoundCounterSettle 轮次结束后结算
func (ac *AirCraft) RoundCounterSettle(roundId int64, maxMultiplier float64) {
	defer logger.Recover()
	ac.mu.RLock()
	ct, ok := ac.roundCounts[roundId]
	ac.mu.RUnlock()
	if ok {
		// 结算
		go ct.Settle(ac.pp.GetGameId(), maxMultiplier)
	}
	// 记录本游戏节点小飞机轮次的运行状态日志
	ac.updateAvtNodeRound(roundId, maxMultiplier)
}

// Register 玩家登录小飞机分配播报分组
func (ac *AirCraft) Register(tb *games.GameTable) {
	defer logger.Recover()
	newIdx := atomic.AddInt32(&ac.notifierIdx, 1)
	if newIdx >= ac.cfg.Notifiers {
		newIdx = 0
		atomic.StoreInt32(&ac.notifierIdx, 0)
	}
	notifier := ac.notifiers[newIdx]
	notifier.Register(tb)
	tb.QueIdx = newIdx
}

// UnRegister 玩家登出小飞机从播报分组中移除
func (ac *AirCraft) UnRegister(tb *games.GameTable) {
	notifier := ac.notifiers[tb.QueIdx]
	if notifier != nil {
		notifier.Unregister(tb.Player.ID())
	}
}

// broadcastState 播报小飞机状态
func (ac *AirCraft) broadcastState(state AcState) {
	defer logger.Recover()

	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.KeyChangeState)
	var itemc uint16 = 3
	if state.TimeLeft != 0 {
		itemc = 4
	}
	xw.WriteSfsObjectPre(sfs.ParamId, itemc)
	xw.WriteIntS(constant.KeyNewStateId, state.State)
	xw.WriteIntS(sfs.KeyCode, 200)
	xw.WriteLongS(constant.KeyRoundId, state.RoundId)

	if state.TimeLeft != 0 {
		xw.WriteLongS(constant.KeyTimeLeft, state.TimeLeft)
	}

	xw1 := sfs.XWriter{}
	defer xw1.Reset()
	if state.State == constant.CrashState {
		xw1.WriteUShort(uint16(2))
		xw1.WriteStringS(sfs.CmdKey, constant.KeyRoundChartInfo)
		xw1.WriteSfsObjectPre(sfs.ParamId, 4)
		xw1.WriteIntS(sfs.KeyCode, 200)
		xw1.WriteDoubleS(constant.KeyMaxMultiplier, state.MaxMultiplier)
		xw1.WriteLongS(constant.KeyRoundId, state.RoundId)
		xw1.WriteLongS(constant.KeyEndDate, state.End)
	}
	for _, nt := range ac.notifiers {
		m := response.AcquireMsgO13()
		xw.WriteTo(m)
		nt.Notify(m)
		if state.State == constant.CrashState {
			m1 := response.AcquireMsgO13()
			xw1.WriteTo(m1)
			nt.Notify(m1)
		}
	}
}

// broadcastBetsNotify 播报小飞机其他数据
func (ac *AirCraft) broadcastBetsNotify(data *gamep.BetsNotify) {
	defer logger.Recover()

	for _, nt := range ac.notifiers {
		m := response.AcquireMsgO13()
		m.Write(data.Data)
		nt.Notify(m)
	}
}

func (ac *AirCraft) Destroy() {
	ac.stopCh <- struct{}{}
	ac.stopMulti <- struct{}{}
	for _, nt := range ac.notifiers {
		nt.Close()
	}
	if ac.multiSub != nil {
		ac.multiSub.Unsubscribe()
		ac.multiSub.Drain()
	}
	if ac.betsSub != nil {
		ac.betsSub.Unsubscribe()
		ac.betsSub.Drain()
	}
	ac.timerWheel.Stop()
	gloader.CClient().UnWatch(ac.stateDir)
	actorservice.Stop(ac.pid)
}
